﻿using System;
using System.Drawing;
using System.Windows.Forms;
namespace Common
{
    public enum ViewMode
    {
        BestFit, OriginalSize, CustomZoomFactor
    }

    public class View : Panel
    {
        private Document doc;
        private Bitmap bitBuffer;
        private Bitmap bitScreen;
        private float factor;
        private int oriX = 0, oriY = 0;
        private int dispWidth, dispHeight;
        private bool mousePressed = false;
        private int mouseOriX, mouseOriY;
        private ViewMode viewMode;

        public View()
        {
            Init(new Document());
        }

        public View(Document document)
        {
            Init(document);
        }

        private void Init(Document document)
        {
            doc = document;
            BackColor = SystemColors.Window;
            bitBuffer = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);
            bitScreen = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);
            Paint += new PaintEventHandler(ViewPaint);
            Resize += new EventHandler(ViewResize);
            MouseDown += new MouseEventHandler(ViewMouseDown);
            MouseMove += new MouseEventHandler(ViewMouseMove);
            MouseUp += new MouseEventHandler(ViewMouseUp);

            BestFit();  
        }

        public ViewMode ViewMode
        {
            get
            {
                return viewMode;
            }

            set
            {
                viewMode = value;
                if (viewMode == ViewMode.BestFit)
                    BestFit();
                else if (viewMode == ViewMode.OriginalSize)
                    OriginalSize();
                else
                {
                    ResetImagePosition();
                    Refresh();
                }
            }
        }

        protected void OriginalSize()
        {
            factor = 1.0f;
            ResetImagePosition();
            OnZoomRateChanged();
            Refresh();
        }

        protected void BestFit()
        {
            dispHeight = doc.GetBitmap().Height;
            dispWidth = doc.GetBitmap().Width;
            float picRatio = dispWidth / (float)dispHeight;
            float pnlRatio = ClientRectangle.Width / (float)ClientRectangle.Height;
            if (picRatio > pnlRatio)
            {
                factor = ClientRectangle.Width / (float)dispWidth;
            }
            else
            {
                factor = ClientRectangle.Height / (float)dispHeight;
            }
            ResetImagePosition();
            OnZoomRateChanged();
            Refresh();
        }

        public delegate void ImageMouseEvent(int x, int y);
        public event ImageMouseEvent ImageMouseDown = null, ImageMouseUp = null, ImageMouseMove = null;
       
        bool enableMoving = true;
        public bool EnableMoving
        {
            get
            {
                return enableMoving;
            }
            set
            {
                enableMoving = value;
            }
        }
        void OnImageMouseDown(int x, int y)
        {
            if (ImageMouseDown != null)
                ImageMouseDown(x, y);
        }

        void OnImageMouseUp(int x, int y)
        {
            if (ImageMouseUp != null)
                ImageMouseUp(x, y);
        }

        void OnImageMouseMove(int x, int y)
        {
            if (ImageMouseMove != null)
                ImageMouseMove(x, y);
        }

        void ViewMouseUp(object sender, MouseEventArgs e)
        {
            mousePressed = false;
            OnImageMouseUp((int)Math.Round((e.X - oriX)/factor), (int)Math.Round((e.Y-oriY)/factor));
        }

        void ViewMouseMove(object sender, MouseEventArgs e)
        {
            OnImageMouseMove((int)Math.Round((e.X - oriX) / factor), (int)Math.Round((e.Y - oriY) / factor));
            if (mousePressed && enableMoving)
            {
                int dx = e.X - mouseOriX;
                int dy = e.Y - mouseOriY;
                if (dispWidth > ClientRectangle.Width)
                {
                    int nx = oriX + dx;
                    if (nx > 0)
                        nx = 0;
                    if (nx + dispWidth < ClientRectangle.Width)
                        nx = ClientRectangle.Width - dispWidth;
                    oriX = nx;
                }
                if (dispHeight > ClientRectangle.Height)
                {
                    int ny = oriY + dy;
                    if (ny > 0)
                        ny = 0;
                    if (ny + dispHeight < ClientRectangle.Height)
                        ny = ClientRectangle.Height - dispHeight;
                    oriY = ny;
                }
                mouseOriX = e.X;
                mouseOriY = e.Y;
                Refresh();
            }
        }

        void ViewMouseDown(object sender, MouseEventArgs e)
        {
            OnImageMouseDown((int)Math.Round((e.X - oriX) / factor), (int)Math.Round((e.Y - oriY) / factor));
            mousePressed = true;
            mouseOriX = e.X;
            mouseOriY = e.Y;
        }

        public void ViewResize(object sender, EventArgs e)
        {
            if (ClientRectangle.Width > 0 && ClientRectangle.Height > 0)
            {
                bitBuffer.Dispose();
                bitBuffer = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);
                bitScreen = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);
                if (viewMode == ViewMode.BestFit)
                    BestFit();
                else
                {
                    ResetImagePosition();
                    Refresh();
                }
            }
        }

        public void ViewPaint(object sender, PaintEventArgs e)
        {
            Graphics bg = Graphics.FromImage(bitScreen);
            bg.DrawImageUnscaled(bitBuffer, 0, 0);
            OnDrawOverlay(bg, new Rectangle(oriX, oriY, dispWidth, dispHeight));
            Graphics g = CreateGraphics();
            CreateGraphics().DrawImageUnscaled(bitScreen, 0, 0);
        }

        public delegate void DrawOverlayDelegate(Graphics g, Rectangle imageRect);

        public event DrawOverlayDelegate DrawOverlay = null;

        void OnDrawOverlay(Graphics g, Rectangle imageRect)
        {
            if (DrawOverlay != null)
                DrawOverlay(g, imageRect);
        }

        public Point ImageSpacePointToScreenSpacePoint(Point p)
        {
            Point rs = new Point();
            rs.X = (int)(p.X * factor) + oriX;
            rs.Y = (int)(p.Y * factor) + oriY;
            return rs;
        }

        public override void Refresh() 
        {
            Graphics g = Graphics.FromImage(bitBuffer);
            g.FillRectangle(SystemBrushes.MenuBar, ClientRectangle);
            g.DrawImage(doc.GetBitmap(), new Rectangle(oriX, oriY, dispWidth, dispHeight));
            // OnDrawOverlay(g, new Rectangle(oriX, oriY, dispWidth, dispHeight));
            Invalidate(new Rectangle(0,0,1,1));
        }

        public void RefreshOverlay()
        {
            Invalidate(new Rectangle(0, 0, 1, 1));
            ViewPaint(this, new PaintEventArgs(CreateGraphics(), ClientRectangle));
        }

        public int ZoomFactor
        {
            get
            {
                if (factor < 1.0f)
                {
                    return (int)Math.Round(factor / 0.1f);
                }
                else
                {
                    return Math.Min((int)((factor - 1.0f) / 0.5f) + 10, 20);
                }
            }

            set
            {
                viewMode = ViewMode.CustomZoomFactor;
                if (value <= 10)
                {
                    factor = Math.Max(1,value) / 10.0f;
                }
                else
                {
                    factor = (Math.Min(value,20) - 10) * 0.5f + 1;
                }
                ResetImagePosition();
                OnZoomRateChanged();
                Refresh();
            }
        }

        protected void ResetImagePosition()
        {
            dispWidth = (int)(doc.GetBitmap().Width * factor);
            dispHeight = (int)(doc.GetBitmap().Height * factor);
            oriX = (ClientRectangle.Width - dispWidth) / 2;
            oriY = (ClientRectangle.Height - dispHeight) / 2;
        }

        public float GetZoomRate()
        {
            return factor;
        }

        public delegate void ZoomFactorChangedEvent(int zoom, float rate);
        public event ZoomFactorChangedEvent ZoomFactorChanged;

        protected void OnZoomRateChanged()
        {
            if (ZoomFactorChanged != null)
                ZoomFactorChanged.Invoke(ZoomFactor, factor);
        }


        public void SetDocument(Document document)
        {
            doc = document;
            BestFit();
        }
    }
}
